/*
 * Ingenic IMP SDK emulator test.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 */

#include <CUnit/CUnit.h>
#include <CUnit/Automated.h>
#include <CUnit/Basic.h>
#include <CUnit/Console.h>

#include <unistd.h>
#include <stdlib.h>

#include <imp/imp_log.h>
#include <imp/imp_common.h>
#include <imp/imp_system.h>
#include <imp/imp_framesource.h>
#include <imp/imp_isp.h>

#include <imp/imp_ivs.h>
#include <ivs/ivs_fake.h>
#include <ivs/ivs_smoke.h>
#include <ivs/ivs_face.h>
#include <ivs/ivs_line.h>
#include <ivs/ivs_figure.h>
#include <ivs/ivs_head.h>
#include <ivs/ivs_shade.h>
#include <ivs/ivs_move.h>
#include <ivs/ivs_perm.h>

#include <system/system.h>
#include <system/vbm.h>
#include <emulator/imp_emu_framesource.h>
#include <emulator/imp_emu_encoder.h>

#define TAG "IVSTest"
#define SENSOR_FRAME_RATE			25
#define SENSOR_WIDTH				480
#define SENSOR_HEIGHT				270
#define MAX_FRAMES_TO_SAVE			10
#define	IVS_LINE_ALARM_LAST_TIME	2000 /* ms */
#define IVS_MAX_RECT_NUM			5
#define IVS_LINE_NUM				2

//static IVSPoint dpt[5]={{0,120},{0,240},{640,240},{640,120},{0,120}};
static IVSRect rects[IVS_MAX_RECT_NUM] = {
	{.ul = {640,     0},.br = {  0, 480}},
	{.ul = {  0,     0},.br = {640, 480}},
	{.ul = {320,     0},.br = {320, 480}},
	{.ul = {  0,   240},.br = {640, 240}},
};

#if 1
/* Fake functions */
static inline int isp_init() { return 0;}
static inline int isp_vb_init() { return 0;}
static inline int start_isp() { return 0;}
static inline int encoder_config() { return 0;}
static inline int encoder_enable_channel() { return 0;}
static inline int start_encoder() { return 0;}
static inline int bind() { return 0;}
static inline int Select() { return 0;}

static int ImpSystemInit()
{
	IMP_LOG_DBG(TAG, "ImpSystemInit\n");
	IMP_System_Init();

	return 0;
}

static int ImpSystemExit()
{
	IMP_LOG_DBG(TAG, "ImpSystemExit\n");
	IMP_System_Exit();
	return 0;
}

static int ImpFrameSourceInit()
{
	int ret;

	/* Configure channel 0 attr */
	IMPFSChnAttr imp_chn_attr;
	imp_chn_attr.pixFmt = PIX_FMT_NV12;
	imp_chn_attr.outFrmRate = SENSOR_FRAME_RATE;
	imp_chn_attr.nrVBs = 4;

	imp_chn_attr.crop.enable = 1;
	imp_chn_attr.crop.top = 128;
	imp_chn_attr.crop.left = 128;
	imp_chn_attr.crop.width = SENSOR_WIDTH;
	imp_chn_attr.crop.height = SENSOR_HEIGHT;

	imp_chn_attr.scaler.enable = 0;

	imp_chn_attr.picWidth = SENSOR_WIDTH;
	imp_chn_attr.picHeight = SENSOR_HEIGHT;

	/* create channel 0*/
	ret = IMP_EmuFrameSource_CreateChn(0, &imp_chn_attr);
	if(ret < 0){
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_CreateChn(chn0) error!\n");
		return -1;
	}

	/* Check channel 0 attr */
	IMPFSChnAttr imp_chn_attr_check;
	ret = IMP_EmuFrameSource_GetChnAttr(0, &imp_chn_attr_check);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_GetChnAttr(1) error: %d\n", ret);
		return -1;
	}
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.picWidth=%d\n", imp_chn_attr_check.picWidth);
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.picHeight=%d\n", imp_chn_attr_check.picHeight);
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.pixFmt=%d\n", imp_chn_attr_check.pixFmt);
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.outFrmRate=%d\n", imp_chn_attr_check.outFrmRate);

	IMP_LOG_DBG(TAG, "%s(): OK.\n", __func__);

	return 0;
}

static int ImpFrameSourceExit()
{
	IMP_EmuFrameSource_DestroyChn(0);
	return 0;
}

static int ImpStreamOn()
{
	int ret = 0;
	/* Enable channels */
	ret = IMP_EmuFrameSource_EnableChn(0);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_EnableChn(0) error: %d\n", ret);
		return -1;
	}

	return 0;
}
static int ImpStreamOff()
{
	int ret = 0;
	/* Disable channels */
	ret = IMP_EmuFrameSource_DisableChn(0);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_EnableChn(0) error: %d\n", ret);
		return -1;
	}

	return 0;
}

static int ivs_init(int id, IMPIVSInterface **pphandler)
{
	int ret = 0;
	void *param = NULL;
	IMPIVSInterface *handler;

	/* Creat IVS Group */
	ret = IMP_IVS_CreateGroup(0);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_CreateGroup(0) failed\n");
		return -1;
	}

	switch (id) {
		case IVS_MOVE_DETECT:
			param = (move_param_input_t *)calloc(1, sizeof(move_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc move_param_input_t failed!\n");
				return -1;
			}
			((move_param_input_t *)param)->frameInfo.width = SENSOR_WIDTH;
			((move_param_input_t *)param)->frameInfo.height = SENSOR_HEIGHT;
			break;
		case IVS_LINE_DETECT:
			{
				int m = 0;
				param = (line_param_input_t *)calloc(1, sizeof(line_param_input_t));
				if (param == NULL) {
					IMP_LOG_ERR(TAG, "calloc line_param_input_t failed!\n");
					return -1;
				}
				((line_param_input_t*)param)->cntLine = IVS_LINE_NUM;
				for (m = 0; m < IVS_LINE_NUM; m++) {
					((line_param_input_t*)param)->lineDetector[m].line.p0.x = rects[m].ul.x;
					((line_param_input_t*)param)->lineDetector[m].line.p0.y = rects[m].ul.y;
					((line_param_input_t*)param)->lineDetector[m].line.p1.x = rects[m].br.x;
					((line_param_input_t*)param)->lineDetector[m].line.p1.y = rects[m].br.x;
				}
			}
			break;
#if 0
		case IVS_PERM_DETECT:
			{
				int m = 0, permcnt = 0;
				param = (perm_param_input_t *)calloc(1, sizeof(perm_param_input_t));
				if (param == NULL) {
					IMP_LOG_ERR(TAG, "calloc perm_param_input_t failed!\n");
					return -1;
				}
				permcnt=((perm_param_input_t*)param)->permcnt= 1;
				for(m = 0; m < permcnt; m++) {
					((perm_param_input_t*)param)->perms[m].pcnt = sizeof(dpt) / sizeof(dpt[0]);
					((perm_param_input_t*)param)->perms[m].p = dpt;
				}
			}
			break;
#endif
		case IVS_FIGURE_DETECT:
			param = (figure_param_input_t *)calloc(1, sizeof(figure_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc figure_param_input_t failed!\n");
				return -1;
			}
			break;
		case IVS_FACE_DETECT:
			param = (face_param_input_t *)calloc(1, sizeof(face_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc face_param_input_t failed!\n");
				return -1;
			}
			break;
#if 0
		case IVS_SMOKE_DETECT:
			param = (smoke_param_input_t *)calloc(1, sizeof(smoke_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc smoke_param_input_t failed!\n");
				return -1;
			}
			break;
		case IVS_SHADE_DETECT:
			param = (shade_param_input_t *)calloc(1, sizeof(shade_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc shade_param_input_t failed!\n");
				return -1;
			}
			break;
		case IVS_HEAD_DETECT:
			param = (head_param_input_t *)calloc(1, sizeof(head_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc head_param_input_t failed!\n");
				return -1;
			}
			break;
#endif
#ifdef CONFIG_DBG_FAKE
		case IVS_FAKE_DETECT:
			param = (fake_param_input_t *)calloc(1, sizeof(fake_param_input_t));
			if (param == NULL) {
				IMP_LOG_ERR(TAG, "calloc fake_param_input_t failed!\n");
				return -1;
			}
			break;
#endif
		default:
			IMP_LOG_WARN(TAG, "unsupported ivs id:%d\n", id);
			break;
	}

	if (param == NULL) {
		IMP_LOG_WARN(TAG, "calloc ivs id %d failed!\n", id);
		return -1;
	}

	handler = IMP_IVS_CreateInterface((IMPIVSFuncEnum)id, param);
	if (handler == NULL) {
		IMP_LOG_ERR(TAG, "IMP_IVS_CreateInterface(%d)\n", id);
		return -1;
	}

	ret = IMP_IVS_CreateChn(id, handler);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_CreateChn(%d, %p) failed\n",	id, handler);
		return -1;
	}

	ret = IMP_IVS_RegisterChn(0, id);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_RegisterChn(0, %d) failed\n", 0, id);
		return -1;
	}

	free(param);
	*pphandler = handler;

	return 0;
}

static int ivs_exit(int id, IMPIVSInterface *handler)
{
	int ret = -1;
	ret = IMP_IVS_UnRegisterChn(id);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_UnRegisterChn(0, %d) failed\n", 0, id);
		return -1;
	}

	ret = IMP_IVS_DestroyChn(id);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_DestroyChn(%d) failed\n",	id);
		return -1;
	}

	IMP_IVS_DestroyInterface(handler);
	IMP_IVS_DestroyGroup(0);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_DestroyGroup(0) failed\n");
		return -1;
	}

	return 0;
}

static int do_get_stream(int id)
{
	int i = 0, m = 0, timeout = 1000000/SENSOR_FRAME_RATE;
	int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
	void* result;

	if (IMP_IVS_StartRecvPic(id) < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_StartRecvPic failed\n", __func__, __LINE__);
		goto err_IMP_IVS_StartRecvPic;
	}

	for (i = 0; i < MAX_FRAMES_TO_SAVE; i++) {
		if (IMP_IVS_PollingResult(id, timeout) < 0) {
			IMP_LOG_ERR(TAG, "IMP_IVS_PollingResult failed\n", __func__, __LINE__);
			goto err_IMP_IVS_PollingResult;
		}

		if (IMP_IVS_GetResult(id, (void **)&result) < 0) {
			IMP_LOG_ERR(TAG, "IMP_IVS_GetResult failed\n", __func__, __LINE__);
			goto err_IMP_IVS_GetResult;
		}

		switch (id) {
			case IVS_MOVE_DETECT:
				{
					IMP_LOG_INFO(TAG, "move detect \n");
					move_param_output_t* r = (move_param_output_t*)result;
					printf("move detect result = %d, r->count = %d\n", r->ret, r->count);
					if (r->count > 0) {
						for(m = 0; m < r->count; m++){
							x0 = r->rects[m].ul.x;
							y0 = r->rects[m].ul.y;
							x1 = r->rects[m].br.x;
							y1 = r->rects[m].br.y;
							printf("move detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
						}
					}
				}
				break;
			case IVS_LINE_DETECT:
				{
					IMP_LOG_INFO(TAG, "line detect \n");
					line_param_output_t* r = (line_param_output_t*)result;
					printf("line detect result = %d, r->count=%d\n", r->ret, r->count);
					if (r->count > 0) {
						for(m = 0; m < r->count; m++){
							x0 = r->rects[m].ul.x;
							y0 = r->rects[m].ul.y;
							x1 = r->rects[m].br.x;
							y1 = r->rects[m].br.y;
							printf("line detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
						}
					}
					for (m = 0; m < IVS_MAX_SIZE; m++) {
						if (r->is_alarmed[m] > 0) {
							printf("line detect line id %d alarmed\n", m);
						}
					}
				}
				break;
#if 0
			case IVS_PERM_DETECT:
				{
					IMP_LOG_INFO(TAG, "perm detect \n");
					perm_param_output_t* r = (perm_param_output_t*)result;
					printf("perm detect result = %d, r->rectcnt = %d\n", r->ret, r->rectcnt);
					if (r->ret >= 0) {
						for (m = 0; m < 1; m++) {
							printf("perm detect r->is_alarmed[%d]=%d\n", m, r->is_alarmed[m]);
						}
						if (r->rectcnt > 0) {
							for (m = 0; m < r->rectcnt; m++) {
								x0 = r->rects[m].ul.x;
								y0 = r->rects[m].ul.y;
								x1 = r->rects[m].br.x;
								y1 = r->rects[m].br.y;
								printf("perm detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
							}
						}
					}
				}
				break;
#endif
			case IVS_FIGURE_DETECT:
				{
					IMP_LOG_INFO(TAG, "figure detect \n");
					figure_param_output_t* r = (figure_param_output_t* )result;
					if(r->count > 0) {
						for (m = 0; m < r->count; m++) {
							x0 = r->rects[m].ul.x;
							y0 = r->rects[m].ul.y;
							x1 = r->rects[m].br.x;
							y1 = r->rects[m].br.y;
							printf("figure detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
						}
					}
				}
				break;
			case IVS_FACE_DETECT:
				{
					IMP_LOG_INFO(TAG, "face detect \n");
					face_param_output_t* r = (face_param_output_t* )result;
					if(r->count > 0) {
						for(m = 0; m < r->count; m++){
							x0 = r->rects[m].ul.x;
							y0 = r->rects[m].ul.y;
							x1 = r->rects[m].br.x;
							y1 = r->rects[m].br.y;
							printf("face detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
						}
					}
				}
				break;
#if 0
			case IVS_SMOKE_DETECT:
				{
					IMP_LOG_INFO(TAG, "smoke detect \n");
					smoke_param_output_t* r = (smoke_param_output_t* )result;
					if(r->sum > 0) {
						for (m = 0; m < r->sum; m++){
							x0 = r->rects[m].ul.x;
							y0 = r->rects[m].ul.y;
							x1 = r->rects[m].br.x;
							y1 = r->rects[m].br.y;
							printf("smoke detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
						}
					}
				}
				break;
			case IVS_SHADE_DETECT:
				{
					IMP_LOG_INFO(TAG, "shade detect \n");
					shade_param_output_t* r = (shade_param_output_t*)result;
					printf("shade detect result = %d\n", r->flag);
				}
				break;
			case IVS_HEAD_DETECT:
				{
					IMP_LOG_INFO(TAG, "head detect \n");
					head_param_output_t* r = (head_param_output_t* )result;
					if(r->count > 0) {
						for(m = 0; m < r->count; m++){
							x0 = r->rects[m].ul.x;
							y0 = r->rects[m].ul.y;
							x1 = r->rects[m].br.x;
							y1 = r->rects[m].br.y;
							printf("head detect rect[%d]:(%d, %d, %d, %d)\n", m, x0, y0, x1, y1);
						}
					}
				}
				break;
#endif
#ifdef CONFIG_DBG_FAKE
			case IVS_FAKE_DETECT:
				{
					IMP_LOG_INFO(TAG, "fake detect \n");
					fake_param_output_t *fake = (fake_param_output_t* )result;
					printf("fake detect result->count = %d\n", fake->count);
					break;
				}
				break;
#endif
			default:
				IMP_LOG_WARN(TAG, "unsupported ivs id:%d\n", id);
				break;
		}
		if (IMP_IVS_ReleaseResult(id, result) < 0) {
			IMP_LOG_ERR(TAG, "IMP_IVS_GetResult failed\n", __func__, __LINE__);
			goto err_IMP_IVS_ReleaseResult;
		}
		printf("ivs frame %d finished\n", i);
	}

	if (IMP_IVS_StopRecvPic(id) < 0) {
		IMP_LOG_ERR(TAG, "IMP_IVS_StopRecvPic failed\n", __func__, __LINE__);
		goto err_IMP_IVS_StopRecvPic;
	}

	return 0;

err_IMP_IVS_StopRecvPic:
err_IMP_IVS_ReleaseResult:
err_IMP_IVS_GetResult:
err_IMP_IVS_PollingResult:
	IMP_IVS_StopRecvPic(id);
err_IMP_IVS_StartRecvPic:
	return -1;
}

static int ImpBindModules()
{
	IMPCell framesource = {DEV_ID_EMU_FS, 0, 0};
	IMPCell ivs = { DEV_ID_IVS, 0, 0};

	IMP_System_Bind(&framesource, &ivs);
	system_bind_dump();

	/* Bind modules here. */
	return 0;
}
static int ImpUnBindModules()
{
	IMPCell framesource = {DEV_ID_EMU_FS, 0, 0};
	IMPCell ivs = { DEV_ID_IVS, 0, 0};

	IMP_System_UnBind(&framesource, &ivs);

	/* Bind modules here. */
	return 0;
}

static void test_ivs(id) /* SYS+emuFS+IVS */
{
	int ret;
	IMPIVSInterface *handler;

	ret = ImpSystemInit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP system init failed\n");
		goto error;
	}

	ret = ImpFrameSourceInit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP ISP init failed\n");
		goto error;
	}

	ret = ivs_init(id, &handler);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "ivs_init failed\n");
		goto error;
	}

	ret = ImpBindModules();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP Encoder init failed\n");
		goto error;
	}

	ret = ImpStreamOn();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP stream on error\n");
		goto error;
	}
	ret = do_get_stream(id);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "do_get_stream error\n");
		goto error;
	}
	ret = ImpStreamOff();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP stream off error\n");
		goto error;
	}
	ivs_exit(id, handler);
	ImpUnBindModules();
	ImpFrameSourceExit();
	ImpSystemExit();

	CU_PASS();

	return;
error:
	CU_FAIL();
}

void test_ivs_move(void)
{
	test_ivs(IVS_MOVE_DETECT);
}

void test_ivs_line(void)
{
	test_ivs(IVS_LINE_DETECT);
}

#if 0
void test_ivs_perm(void)
{
	test_ivs(IVS_PERM_DETECT);
}
#endif

void test_ivs_figure(void)
{
	test_ivs(IVS_FIGURE_DETECT);
}

void test_ivs_face(void)
{
	test_ivs(IVS_FACE_DETECT);
}

#if 0
void test_ivs_smoke(void)
{
	test_ivs(IVS_SMOKE_DETECT);
}

void test_ivs_shade(void)
{
	test_ivs(IVS_SHADE_DETECT);
}

void test_ivs_head(void)
{
	test_ivs(IVS_HEAD_DETECT);
}
#endif

#ifdef CONFIG_DBG_FAKE
void test_ivs_fake(void)
{
	test_ivs(IVS_FAKE_DETECT);
}
#endif
#else
static void test_case_1()
{
	CU_FAIL();
}
#endif
CU_TestInfo EmulatorIVSTest[] = {
	{"SYS+emuFS+IVS_MOVE", test_ivs_move},
	{"SYS+emuFS+IVS_LINE", test_ivs_line},
#if 0
	{"SYS+emuFS+IVS_PERM", test_ivs_perm},
#endif
	{"SYS+emuFS+IVS_FIGURE", test_ivs_figure},
	{"SYS+emuFS+IVS_FACE", test_ivs_face},
#if 0
	{"SYS+emuFS+IVS_SMOKE", test_ivs_smoke},
	{"SYS+emuFS+IVS_SHADE", test_ivs_shade},
	{"SYS+emuFS+IVS_HEAD", test_ivs_head},
#endif
#ifdef CONFIG_DBG_FAKE
	{"SYS+emuFS+IVS_FAKE", test_ivs_fake},
#endif
	CU_TEST_INFO_NULL,
};
